package org.xx.armory.config;

import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.digester3.binder.AbstractRulesModule;
import org.apache.commons.digester3.binder.LinkedRuleBuilder;
import org.apache.commons.digester3.binder.RulesModule;
import org.apache.logging.log4j.Level;
import org.xml.sax.SAXException;
import org.xx.armory.config.app.AppSection;
import org.xx.armory.config.app.CategorySection;
import org.xx.armory.config.data.DataSection;
import org.xx.armory.config.data.RedisSection;
import org.xx.armory.config.log4j2.ConsoleAppenderSection;
import org.xx.armory.config.log4j2.LevelConverter;
import org.xx.armory.config.log4j2.Log4j2Section;
import org.xx.armory.config.log4j2.LoggerSection;
import org.xx.armory.config.log4j2.RedisAppenderSection;
import org.xx.armory.config.log4j2.RollingFileAppenderSection;
import org.xx.armory.config.log4j2.RootLoggerSection;
import org.xx.armory.config.security.AuthenticationSection;
import org.xx.armory.config.security.KeySection;
import org.xx.armory.config.security.SecuritySection;
import org.xx.armory.config.service.ServiceSection;

import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.io.IOException;
import java.io.InputStream;

public final class ConfigurationRulesModule
        extends AbstractRulesModule
        implements RulesModule {
    private static final String XSD_FILE_NAME = "/armory.xsd";

    private static void setNestedProperties(
            LinkedRuleBuilder builder
    ) {
        builder.callMethod("setProperty")
               .withParamTypes(String.class, String.class)
               .then().callParam().fromAttribute("name").ofIndex(0)
               .then().callParam().fromAttribute("value").ofIndex(1);
    }

    /**
     * 加载用于解析配置文件的架构。
     *
     * @return 用于解析配置文件的架构。
     * @throws IOException
     *         加载配置文件时出现错误。
     * @throws SAXException
     *         解析配置文件的内容时出现错误。
     */
    public Schema loadSchema()
            throws IOException, SAXException {
        try (final InputStream xsd = ConfigurationRulesModule.class.getResourceAsStream(XSD_FILE_NAME)) {
            return SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(new StreamSource(xsd));
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void configure() {
        ConvertUtils.register(new Base64Converter(), byte[].class);
        ConvertUtils.register(new LevelConverter(), Level.class);

        rulesBinder().forPattern("configuration").createObject()
                     .ofType(Configuration.class);

        // app
        rulesBinder().forPattern("configuration/app").createObject()
                     .ofType(AppSection.class)
                     .then().setNext("setApp");
        rulesBinder().forPattern("configuration/app/category").createObject()
                     .ofType(CategorySection.class)
                     .then().setProperties()
                     .then().setNext("addCategory");
        rulesBinder().forPattern("configuration/app/category/prop")
                     .callMethod("setProperty").withParamTypes(String.class, String.class)
                     .then().callParam().ofIndex(0).fromAttribute("name")
                     .then().callParam().ofIndex(1).fromAttribute("value");

        // security
        rulesBinder().forPattern("configuration/security").createObject()
                     .ofType(SecuritySection.class)
                     .then().setProperties()
                     .then().setNext("setSecurity");
        rulesBinder().forPattern("configuration/security/keys/key").createObject()
                     .ofType(KeySection.class)
                     .then().setProperties()
                     .then().setNext("addKey");
        rulesBinder().forPattern("configuration/security/authentication").createObject()
                     .ofType(AuthenticationSection.class)
                     .then().setProperties()
                     .then().setNext("setAuthentication");

        // data
        rulesBinder().forPattern("configuration/data").createObject()
                     .ofType(DataSection.class)
                     .then().setProperties()
                     .then().setNext("setData");

        // data source
        rulesBinder().forPattern("configuration/data/dataSource").createObject()
                     .ofType(NamedPropertiesSection.class)
                     .then().setProperties()
                     .then().setNext("addDataSource");

        setNestedProperties(rulesBinder().forPattern("configuration/data/dataSource/prop"));

        // redis
        rulesBinder().forPattern("configuration/data/redis").createObject()
                     .ofType(RedisSection.class)
                     .then().setProperties()
                     .then().setNext("addRedis");

        // service
        rulesBinder().forPattern("configuration/service").createObject()
                     .ofType(ServiceSection.class)
                     .then().setProperties()
                     .then().setNext("setService");

        // decorator
        rulesBinder().forPattern("configuration/service/decorator").createObject()
                     .ofType(NamedPropertiesSection.class)
                     .then().setProperties()
                     .then().setNext("addDecorator");

        setNestedProperties(rulesBinder().forPattern("configuration/service/decorator/prop"));

        // log4j2
        rulesBinder().forPattern("configuration/log4j2").createObject()
                     .ofType(Log4j2Section.class)
                     .then().setNext("setLog4j2");
        rulesBinder().forPattern("configuration/log4j2/appenders/consoleAppender").createObject()
                     .ofType(ConsoleAppenderSection.class)
                     .then().setProperties()
                     .then().setNext("addAppender");
        rulesBinder().forPattern("configuration/log4j2/appenders/rollingFileAppender").createObject()
                     .ofType(RollingFileAppenderSection.class)
                     .then().setProperties()
                     .then().setNext("addAppender");
        rulesBinder().forPattern("configuration/log4j2/appenders/redisAppender").createObject()
                     .ofType(RedisAppenderSection.class)
                     .then().setProperties()
                     .then().setNext("addAppender");
        rulesBinder().forPattern("configuration/log4j2/loggers/logger").createObject()
                     .ofType(LoggerSection.class)
                     .then().setProperties()
                     .then().setNext("addLogger");
        rulesBinder().forPattern("configuration/log4j2/loggers/root").createObject()
                     .ofType(RootLoggerSection.class)
                     .then().setProperties()
                     .then().setNext("setRoot");
    }
}

